/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.seppiko.pigeon.services;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.AddressException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.seppiko.commons.mail.JakartaMailSender;
import org.seppiko.commons.mail.MimeMessageHelper;
import org.seppiko.commons.utils.StringUtil;
import org.seppiko.pigeon.configuration.PigeonConfiguration;
import org.seppiko.pigeon.exceptions.PigeonMessageException;
import org.seppiko.pigeon.mapper.ImapMapper;
import org.seppiko.pigeon.mapper.RoleMapper;
import org.seppiko.pigeon.mapper.SmtpMapper;
import org.seppiko.pigeon.mapper.UserMapper;
import org.seppiko.pigeon.models.ImapConfigEntity;
import org.seppiko.pigeon.models.MailEntity;
import org.seppiko.pigeon.models.RoleEntity;
import org.seppiko.pigeon.models.SmtpConfigEntity;
import org.seppiko.pigeon.models.UserEntity;
import org.seppiko.pigeon.utils.MailUtil;
import org.seppiko.pigeon.utils.PasswordUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * Mail Service
 *
 * @author Leonard Woo
 */
@Slf4j
@Service
public class MailService {

  private final PigeonConfiguration configuration = PigeonConfiguration.getInstance();

  @Autowired
  private UserMapper userMapper;

  @Autowired
  private RoleMapper roleMapper;

  @Autowired
  private SmtpMapper smtpMapper;

  @Autowired
  private ImapMapper imapMapper;

  @Autowired
  private LogService logService;

  public boolean sender(MailEntity mail, int smtpId, String username, String password)
      throws PigeonMessageException {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    SmtpConfigEntity mailConfig = smtpMapper.query(smtpId, user.getUid());
    mailConfig.setPassword(PasswordUtil.decode(mailConfig.getPassword(), password));
    mail.setFrom(mailConfig.getMailFrom());

    // Mail Sent Record
    if (this.configuration.isMailSpy()) {
      mailSpy(mail);
    }

    return sender(mailConfig, mail);
  }

  public boolean addSmtpConfig(SmtpConfigEntity mailConfig, String username, String password) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    mailConfig.setUid(user.getUid());
    mailConfig.setPassword(PasswordUtil.encode(mailConfig.getPassword(), password));
    return smtpMapper.add(mailConfig) > 0;
  }

  public List<SmtpConfigEntity> querySmtpConfigList(String username, String password) {
    UserEntity userResult = userMapper.queryUserByUserName(username);
    if (userResult == null) {
      return null;
    }
    List<SmtpConfigEntity> mailConfigs = smtpMapper.queryAllByUser(userResult.getUid());
    mailConfigs.forEach(
        mailConfig -> {
          mailConfig.setPassword(PasswordUtil.decode(mailConfig.getPassword(), password));
        });
    return mailConfigs;
  }

  public boolean updateSmtpConfig(SmtpConfigEntity newConfig, String username, String password) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    newConfig.setUid(user.getUid());
    if (StringUtil.hasText(newConfig.getPassword())) {
      newConfig.setPassword(PasswordUtil.encode(newConfig.getPassword(), password));
    }
    return smtpMapper.update(newConfig) > 0;
  }

  public boolean deleteSmtpConfig(int id, String username) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    return smtpMapper.delete(id, user.getUid()) > 0;
  }

  private boolean sender(SmtpConfigEntity mailConfig, MailEntity mail)
      throws PigeonMessageException {
    try {
      JakartaMailSender sender = MailUtil.getMailSender(mailConfig);

      MimeMessageHelper helper = sender.createMimeMessage();
      helper.setFrom(mail.getFrom());
      if (mail.getTo() != null && mail.getTo().length > 0) {
        helper.setTo(MailUtil.parser(mail.getTo()));
      } else {
        throw new PigeonMessageException("To must be NOT NULL");
      }
      if (mail.getCc() != null && mail.getCc().length > 0) {
        helper.setCc(MailUtil.parser(mail.getCc()));
      }
      if (mail.getBcc() != null && mail.getBcc().length > 0) {
        helper.setBcc(MailUtil.parser(mail.getBcc()));
      }
      if (mail.getSubject() != null && StringUtil.hasText(mail.getSubject())) {
        helper.setSubject(mail.getSubject());
      } else {
        throw new PigeonMessageException("Subject must be NOT NULL");
      }
      if (mail.getText() != null) {
        helper.setText(mail.getText(), mail.isHtml());
      } else {
        throw new PigeonMessageException("Text must be NOT NULL");
      }

      sender.send(helper.getMimeMessage());
      return true;
    } catch (AddressException ex) {
      log.info(ex.getMessage());
      throw new PigeonMessageException("Address parser failed");
    } catch (PigeonMessageException ex) {
      throw ex;
    } catch (MessagingException | IllegalAccessException e) {
      log.warn("Mail sent failure", e);
    }
    return false;
  }

  public List<ImapConfigEntity> queryImapConfigList(String username, String password) {
    UserEntity userResult = userMapper.queryUserByUserName(username);
    if (userResult == null) {
      return null;
    }
    List<ImapConfigEntity> mailConfigs = imapMapper.queryAllByUser(userResult.getUid());
    mailConfigs.forEach(
        mailConfig -> {
          mailConfig.setPassword(PasswordUtil.decode(mailConfig.getPassword(), password));
        });
    return mailConfigs;
  }

  public boolean addImapConfig(ImapConfigEntity mailConfig, String username, String password) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    mailConfig.setUid(user.getUid());
    mailConfig.setPassword(PasswordUtil.encode(mailConfig.getPassword(), password));
    return imapMapper.add(mailConfig) > 0;
  }

  public boolean updateImapConfig(ImapConfigEntity newConfig, String username, String password) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    newConfig.setUid(user.getUid());
    if (StringUtil.hasText(newConfig.getPassword())) {
      newConfig.setPassword(PasswordUtil.encode(newConfig.getPassword(), password));
    }
    return imapMapper.update(newConfig) > 0;
  }

  public boolean deleteImapConfig(int id, String username) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return false;
    }
    return imapMapper.delete(id, user.getUid()) > 0;
  }

  public ArrayList<MailEntity> receivers(int imapId, String username, String password) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return null;
    }
    ImapConfigEntity mailConfig = imapMapper.query(imapId, user.getUid());
    mailConfig.setPassword(PasswordUtil.decode(mailConfig.getPassword(), password));
    final ArrayList<MailEntity> mails = MailUtil.mailReceiver(mailConfig);
    if (mails == null || mails.size() < 1) {
      log.info("Not found any unread mail.");
      return null;
    }

    // Mail Receive Record
    if (this.configuration.isMailSpy()) {
      mailSpy(mails);
    }

    return mails;
  }

  public LinkedHashMap<String, List<?>> queryAllConfigMap(String username) {
    UserEntity user = userMapper.queryUserByUserName(username);
    if (user == null) {
      return null;
    }
    List<RoleEntity> roleList = roleMapper.query(user.getUid());
    if (roleList.stream().noneMatch(e -> e.getName().equals("ADMIN"))) {
      return null;
    }
    LinkedHashMap<String, List<?>> map = new LinkedHashMap<>();
    map.put("smtp", smtpMapper.queryAll());
    map.put("imap", imapMapper.queryAll());
    return map;
  }

  private void mailSpy(ArrayList<MailEntity> mails) {
    mails.forEach(this::mailSpy);
  }

  private void mailSpy(MailEntity mail) {
    logService.mailLogger(mail);
  }
}
